#include "talk/examples/crossfirewall/cfcall.h"

using namespace talk_base;

namespace cricket {

	CFCall::CFCall(std::string local_jid, std::string remote_jid, CFSessionClient* cfsession_client)
		: local_jid_(local_jid),
		  remote_jid_(remote_jid),
		  state_(STR_CALL_STATE_INIT),
		  cfsession_client_(cfsession_client)
	{
		session_num_ = 0;
		p2p_enable_ = upnp_enable_ = false;

		data_seq_ = 0;
		data_ = new char[10240];
		datahead_size_ = sizeof(DataHeader);
	}

	CFCall::~CFCall()
	{
		if(data_) { delete data_;  data_ = NULL; }
	}

	/******************************************************************************/
	/*******************************************************************************
                            Call Related function
	*******************************************************************************/
	/******************************************************************************/
	void CFCall::MakeCall()
	{
		AddSession();
	}

	void CFCall::HangUpCall()
	{
		{
			talk_base::CritScope cs(&state_crit_);
			if(state_ >= STR_CALL_STATE_ENDING)  return;
		}
		ChangeCallState(STR_CALL_STATE_ENDING);
		//STR_LOG(INFO)<<"StrCall2("<<remote_jid_.c_str()<<")::HangUp call(session size = "<<sessions_.size()<<").";
		{
			talk_base::CritScope cs(&vector_crit_);
			if(sessions_.size() == 0){
				ChangeCallState(STR_CALL_STATE_ENDED);
				return;
			}

			std::vector<CFSession>::iterator it;
			for(it = sessions_.begin(); it != sessions_.end(); it++)
				(*it).session_->Terminate();
		}
	}

	int CFCall::SendPacket(int ttype, const char* data, size_t len)
	{
		{
			talk_base::CritScope cs(&state_crit_);
			if(state_ != STR_CALL_STATE_READY)  return 0;
		}

		talk_base::CritScope cs(&p2ps_crit_);
		// check state
		{   
			talk_base::CritScope cs(&state_crit_);
			if(len <= 0 || !p2p_enable_)  return 0;
		}
		// add packet header
		DataHeader* head = (DataHeader*)data_;
		head->ex = 0xaf;  head->ttype = ttype;  head->len = len;
		memcpy(data_ + datahead_size_, data, len);
		// send packet
		{
			talk_base::CritScope cs(&vector_crit_);
			if(channels_.size() > 0){
				int now = data_seq_++ % channels_.size();
				channels_[now].pack_send_ ++;
				channels_[now].data_send_ += len;
				channels_[now].pack_send_ts_ ++;
				channels_[now].data_send_ts_ += len;
				return channels_[now].channel_->SendPacket(data_, datahead_size_ + len);
			}
		}
		return 0;
	}

	void CFCall::ChangeCallState(int state)
	{
		talk_base::CritScope cs(&state_crit_);
		if(state_ >= STR_CALL_STATE_READY && state < STR_CALL_STATE_READY) return;

		if(state_ != state){
			//STR_LOG(INFO)<<"StrCall2("<<remote_jid_.c_str()<<")::Call state change from ("<<state_<<" to "<<state<<").";
			state_ = state;
			if(state_ == STR_CALL_STATE_ENDED) p2p_enable_ = upnp_enable_ = false;
			SignalCallStateChange(this, remote_jid_, state_);
		}
	}

	void CFCall::ChangeCallState(int p2p_enable, int upnp_enable)
	{
		talk_base::CritScope cs(&state_crit_);
		if((p2p_enable  != -1 && p2p_enable_  != (bool)p2p_enable) ||
		   (upnp_enable != -1 && upnp_enable_ != (bool)upnp_enable)){
			p2p_enable  = p2p_enable == -1  ? p2p_enable_  : p2p_enable;
			upnp_enable = upnp_enable == -1 ? upnp_enable_ : upnp_enable;
			/*STR_LOG(INFO)<<"StrCall2("<<remote_jid_.c_str()<<")::Call channel state change from ( p("
						 <<p2p_enable_<<" to "<<p2p_enable<<"), u("
				         <<upnp_enable_<<" to "<<upnp_enable<<")).";*/
			if(p2p_enable  != -1)  p2p_enable_  = (bool)p2p_enable;
			if(upnp_enable != -1)  upnp_enable_ = (bool)upnp_enable;

			if(p2p_enable_ || upnp_enable_){
				if(state_ < STR_CALL_STATE_READY)  state_ = STR_CALL_STATE_READY;
				SignalCallStateChange(this, remote_jid_, state_);
			}
		}
	}

	/******************************************************************************/
	/*******************************************************************************
                            Session Related function
	*******************************************************************************/
	/******************************************************************************/
	void CFCall::AddSession()
	{
		std::string stype = NS_JINGLE_RTP;
		char cname[50];  memset(cname, 0, 50);
		sprintf(cname, "rtp%d", ++session_num_);
		InitiateSession(stype, cname);
	}
	
	void CFCall::InitiateSession(const std::string& session_type, const std::string& channel_name) 
	{
		Session *session = cfsession_client_->CreateSession(local_jid_, session_type);
		if(session){
			// initiate session
			SessionDescription* offer = cfsession_client_->CreateOffer(session_type, channel_name);
			session->Initiate(remote_jid_, offer);

			// initiate channel
			{
				talk_base::CritScope cs(&vector_crit_);
				channels_phase_[channel_name] = 0;
			}
			TransportChannel* channel = session->CreateChannel(session_type, channel_name);
			channel->SignalWritableState.connect(this, &CFCall::OnP2PChannelWritableState);
			AcceptSession(session, false, channel_name);
		}
	}

	void CFCall::AcceptSession(Session* session, bool bsent, std::string channel_name)
	{
		if(session == NULL)  return;
		{
			talk_base::CritScope cs(&vector_crit_);
			CFSession sess(session, channel_name);
			sessions_.push_back(sess);
		}

		session->SignalError.connect(this, &CFCall::OnSessionError);
		session->SignalState.connect(this, &CFCall::OnSessionState);
		//session->SignalInfoMessage.connect(this, &CFCall::OnSessionInfo);
		//session->SignalReceivedTerminateReason.connect(this, &CFCall::OnReceivedTerminateReason);

		if(bsent)  session->Accept(cfsession_client_->CreateAnswer(session->remote_description()));
	}

	void CFCall::TerminateSession(Session *session)
	{
		if(session == NULL)  return;
		session->Terminate();
	}

	void CFCall::RejectSession(Session* session)
	{
		if(session == NULL)  return;
		session->Reject("Rejected.");
	}

	/******************************************************************************/
	/*******************************************************************************
                            Session Related callback function
	*******************************************************************************/
	/******************************************************************************/
	void CFCall::OnSessionIncoming(Session* session)
	{
		std::string channel_name = cfsession_client_->GetSessionChannelName(session->remote_description());
		//STR_LOG(INFO)<<"StrCall2("<<remote_jid_.c_str()<<")::Session(0x"<<std::hex<<session<<") incoming, and channel("<<channel_name.c_str()<<").";

		if(!channel_name.empty()){
			{
				talk_base::CritScope cs(&vector_crit_);
				channels_phase_[channel_name] = 0;
			}
			TransportChannel* channel = session->CreateChannel(session->content_type(), channel_name);
			channel->SignalWritableState.connect(this, &CFCall::OnP2PChannelWritableState);
			AcceptSession(session, true, channel_name);
		}
		else  RejectSession(session);
	}

	void CFCall::OnSessionDestroy(Session* session)
	{
		talk_base::CritScope cs(&vector_crit_);
		std::vector<CFSession>::iterator it;
		for(it = sessions_.begin(); it != sessions_.end(); it++){
			if((*it).session_ ==  session){
				// erase normal or relay channel
				std::vector<CFTransportChannel>::iterator iter;
				for(iter = channels_.begin(); iter != channels_.end(); iter ++){
					if((*iter).channel_ == session->GetChannel(session->content_type(), (*it).channel_name_)){
						channels_.erase(iter);
						break;
					}
				}
				// erase session
				sessions_.erase(it);
				break;
			}
		}
		if(sessions_.size() == 0)  ChangeCallState(STR_CALL_STATE_ENDED);
	}

	void CFCall::OnSessionState(BaseSession* session, Session::State state)
	{
		//STR_LOG(INFO)<<"StrCall2("<<remote_jid_.c_str()<<")::Session(0x"<<std::hex<<session<<") state("<<std::oct<<state<<").";
		switch(state)
		{
		case Session::STATE_SENTINITIATE:          // sent initiate, waiting for Accept or Reject
			break;
		case Session::STATE_RECEIVEDINITIATE:      // received an initiate. Call Accept or Reject
			break;

		case Session::STATE_SENTACCEPT:            // sent accept. begin connecting transport
			break;
		case Session::STATE_RECEIVEDACCEPT:		  // received accept. begin connecting transport
			break;

		case Session::STATE_SENTMODIFY:            // sent modify, waiting for Accept or Reject
			break;
		case Session::STATE_RECEIVEDMODIFY:		  // received modify, call Accept or Reject
			break;

		case Session::STATE_SENTREJECT:			  // sent reject after receiving initiate
			break;
		case Session::STATE_RECEIVEDREJECT:        // received reject after sending initiate
			break;

		case Session::STATE_SENTREDIRECT:          // sent direct after receiving initiate
			break;

		case Session::STATE_SENTTERMINATE:         // sent terminate (any time / either side)
			break;
		case Session::STATE_RECEIVEDTERMINATE:     // received terminate (any time / either side)
			break;

		case Session::STATE_INPROGRESS:            // session accepted and in progress
			break;
		case Session::STATE_DEINIT:                // session is being destroyed
			break;

		//case Session::STATE_SE_OPEN: 		      // channel is opened
		default:
			break;
		}
	}

	void CFCall::OnSessionError(BaseSession *session, Session::Error error)
	{
		TerminateSession((Session*)session);
	}

	/******************************************************************************/
	/*******************************************************************************
                            P2P Related callback function
	*******************************************************************************/
	/******************************************************************************/
	void CFCall::OnP2PChannelWritableState(TransportChannel* channel)
	{
		// find channnel in normal channel vector
		{
			talk_base::CritScope cs(&vector_crit_);
			std::vector<CFTransportChannel>::iterator it;
			for(it = channels_.begin(); it != channels_.end(); it ++){
				if((*it).channel_ == channel){
					if(!channel->writable()){
						//STR_LOG(INFO)<<"StrCall2("<<remote_jid_.c_str()<<")::channel(0x"<<std::hex<<channel<<") not writable.";
						channels_.erase(it);
					}
					return;
				}
			}
		}

		// add channel into channel vector
		if(channel->writable()){
			//STR_LOG(INFO)<<"StrCall2("<<remote_jid_.c_str()<<")::channel(0x"<<std::hex<<channel<<") writable.";
			{
				talk_base::CritScope cs(&vector_crit_);
				CFTransportChannel transchannel(channel);
				channels_.push_back(transchannel);
			}
			ChangeCallState(1, -1);
			channel->SignalReadPacket.connect(this, &CFCall::OnP2PChannelReadPacket);
		}
	}
	
	void CFCall::OnP2PChannelReadPacket(TransportChannel* channel, const char* data, size_t len)
	{
		{
			talk_base::CritScope cs(&state_crit_);
			if(state_ != STR_CALL_STATE_READY)  return;
		}
		if(len <= 0)  return;

		DataHeader* head = (DataHeader*)data;
		if(head->ex == 0xaf)
			OnPacketIncoming(head->ttype, data + datahead_size_, head->len);

		/*{
			talk_base::CritScope cs(&vector_crit_);
			std::vector<CFTransportChannel>::iterator it;
			for(it = channels_.begin(); it != channels_.end(); it ++){
				if((*it).channel_ == channel){
					(*it).pack_recv_ ++;
					(*it).data_recv_ += head->len;
					(*it).pack_recv_ts_ ++;
					(*it).data_recv_ts_ += head->len;
					break;
				}
			}

			if(talk_base::Time() - time_now_ >= 5000){
				time_now_ = talk_base::Time();
				for(it = channels_.begin(); it != channels_.end(); it ++){
					STR_LOG(INFO)<<"Channel("<<(*it).channel_->name()
						         <<")::SendInfo("<<(*it).pack_send_ts_<<"|"<<(*it).pack_send_<<", "<<(*it).data_send_ts_<<"|"<<(*it).data_send_
								 <<"), RecvInfo("<<(*it).pack_recv_ts_<<"|"<<(*it).pack_recv_<<", "<<(*it).data_recv_ts_<<"|"<<(*it).data_recv_
								 <<").";
					(*it).pack_send_ts_ = (*it).data_send_ts_ = (*it).pack_recv_ts_ = (*it).data_recv_ts_ =0;
				}
				STR_LOG(INFO)<<"**************************************************************************************";
			}
		}*/
	}

	void CFCall::OnPacketIncoming(int ttype, const char* data, size_t len)
	{
		//SignalPakcetIncoming(remote_jid_, ttype, data, len);
	}
}